using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline.Processors;
using System.ComponentModel;


namespace ContentPipelineExtension
{
    /// <summary>
    /// This class will be instantiated by the XNA Framework Content Pipeline
    /// to apply custom processing to content data, converting an object of
    /// type TInput to TOutput. The input and output types may be the same if
    /// the processor wishes to alter data without changing its type.
    ///
    /// This should be part of a Content Pipeline Extension Library project.
    /// </summary>
    /// 

    [ContentProcessor(DisplayName = "ContentPipelineExtension.CustomModelProcessor")]
    public class CustomModelProcessor : ModelProcessor
    {
        /// <summary>
        /// Use our custom MaterialProcessor
        /// to convert all the materials on this model.
        /// </summary>
        /// 

        String directory;
        public const string normalMapKey = "NormalMap";
        public const string specularMapKey = "SpecularMap";
        private string normalMapTexture;
        private string specularMapTexture;

        /// <summary>
        /// We override this property from the base processor and force it to always
        /// return true: tangent frames are required for normal mapping, so they should
        /// not be optional.
        /// </summary>
        [Browsable(false)]
        public override bool GenerateTangentFrames
        {
            get { return true; }
            set { }
        }

        /// <summary>
        /// The user can set this value in the property grid. If it is set, the model
        /// will use this value for its normal/specular map texture, overriding anything in the
        /// opaque data. We use the display name and description attributes to control
        /// how the property appears in the UI.
        /// </summary>
        [DisplayName("Normal Map Texture")]
        [Description("If set, this file will be used as the normal map on the model, " +
        "overriding anything found in the opaque data.")]
        [DefaultValue("")]
        public string NormalMapTexture
        {
            get { return normalMapTexture; }
            set { normalMapTexture = value; }
        }

        [DisplayName("Specular Map Texture")]
        [Description("If set, this file will be used as the specular map on the model, " +
        "overriding anything found in the opaque data.")]
        [DefaultValue("")]
        public string SpecularMapTexture
        {
            get { return specularMapTexture; }
            set { specularMapTexture = value; }
        }


        // Process
        public override ModelContent Process(NodeContent input,
            ContentProcessorContext context)
        {
            if (input == null)
            {
                throw new ArgumentNullException("input");
            }
            directory = Path.GetDirectoryName(input.Identity.SourceFilename) ;

            LookUpTextures(input);
            return base.Process(input, context);
        }

        // Look up the textures we want to apply.
        private void LookUpTextures(NodeContent node)
        {
            MeshContent mesh = node as MeshContent;
            if (mesh != null)
            {
                // Find the paths to the textures used by the model

                //this will contatin the path to the normal map texture
                string normalMapPath;

                //If the NormalMapTexture property is set, we use that normal map for all meshes in the model.
                //This overrides anything else
                if (!String.IsNullOrEmpty(NormalMapTexture))
                {
                    normalMapPath = NormalMapTexture;
                }
                else
                {
                    //If NormalMapTexture is not set, we look into the opaque data of the model, 
                    //and search for a texture with the key equal to NormalMapKey
                    normalMapPath = mesh.OpaqueData.GetValue<string>(normalMapKey, null);
                }
                //if the NormalMapTexture Property was not used, and the key was not found in the model, than normalMapPath would have the value null.
                if (normalMapPath == null)
                {
                    //If a key with the required name is not found, we make a final attempt, 
                    //and search, in the same directory as the model, for a texture named 
                    //meshname_n.tga, where meshname is the name of a mesh inside the model.
                    normalMapPath = Path.Combine(directory, mesh.Name + "_n.tga");
                    if (!File.Exists(normalMapPath))
                    {
                        //if this fails also (that texture does not exist), 
                        //then we use a default texture, named null_normal.tga
                        normalMapPath = "Models\\null_normal.tga";
                    }
                }
                else
                {
                    normalMapPath = Path.Combine(directory, normalMapPath);
                }

                string specularMapPath;

                //If the SpecularMapTexture property is set, we use it
                if (!String.IsNullOrEmpty(specularMapTexture))
                {
                    specularMapPath = specularMapTexture;
                }
                else
                {
                    //If SpecularMapTexture is not set, we look into the opaque data of the model, 
                    //and search for a texture with the key equal to specularMapKey
                    specularMapPath = mesh.OpaqueData.GetValue<string>(specularMapKey, null);
                }

                if (specularMapPath == null)
                {
                    //we search, in the same directory as the model, for a texture named 
                    //meshname_s.tga
                    specularMapPath = Path.Combine(directory, mesh.Name + "_s.tga");
                    if (!File.Exists(specularMapPath))
                    {
                        //if this fails also (that texture does not exist), 
                        //then we use a default texture, named null_specular.tga
                        specularMapPath = "Models\\null_specular.tga";
                    }
                }
                else
                {
                    specularMapPath = Path.Combine(directory, specularMapPath);
                }

                //add the keys to the material, so they can be used by the shader
                foreach (GeometryContent geometry in mesh.Geometry)
                {
                    //in some .fbx files, the key might be found in the textures collection, but not
                    //in the mesh, as we checked above. If this is the case, we need to get it out, and
                    //add it with the "NormalMap" key
                    if (geometry.Material.Textures.ContainsKey(normalMapKey))
                    {
                        ExternalReference<TextureContent> texRef = geometry.Material.Textures[normalMapKey];
                        geometry.Material.Textures.Remove(normalMapKey);
                        geometry.Material.Textures.Add(normalMapKey, texRef);
                    }
                    else
                        geometry.Material.Textures.Add(normalMapKey,
                                        new ExternalReference<TextureContent>(normalMapPath));

                    if (geometry.Material.Textures.ContainsKey(specularMapKey))
                    {
                        ExternalReference<TextureContent> texRef = geometry.Material.Textures[specularMapKey];
                        geometry.Material.Textures.Remove(specularMapKey);
                        geometry.Material.Textures.Add(specularMapKey, texRef);
                    }
                    else
                        geometry.Material.Textures.Add(specularMapKey,
                                    new ExternalReference<TextureContent>(specularMapPath));
                }
            }

            // go through all children and apply LookUpTextures recursively
            foreach (NodeContent child in node.Children)
            {
                LookUpTextures(child);
            }
        }

        // acceptableVertexChannelNames are the inputs that the normal map effect
        // expects.  The NormalMappingModelProcessor overrides ProcessVertexChannel
        // to remove all vertex channels which don't have one of these four
        // names.
        static IList<string> acceptableVertexChannelNames =
            new string[]
            {
                VertexChannelNames.TextureCoordinate(0),
                VertexChannelNames.Normal(0),
                VertexChannelNames.Binormal(0),
                VertexChannelNames.Tangent(0)
            };

        /// <summary>
        /// As an optimization, ProcessVertexChannel is overriden to remove data which
        /// is not used by the vertex shader.
        /// </summary>
        protected override void ProcessVertexChannel(GeometryContent geometry,
            int vertexChannelIndex, ContentProcessorContext context)
        {
            String vertexChannelName =
                geometry.Vertices.Channels[vertexChannelIndex].Name;

            // if this vertex channel has an acceptable names, process it as normal.
            if (acceptableVertexChannelNames.Contains(vertexChannelName))
            {
                base.ProcessVertexChannel(geometry, vertexChannelIndex, context);
            }
            // otherwise, remove it from the vertex channels; it's just extra data
            // we don't need.
            else
            {
                geometry.Vertices.Channels.Remove(vertexChannelName);
            }
        }
       
        protected override MaterialContent ConvertMaterial(MaterialContent p_material,
                                                           ContentProcessorContext p_context)
        {
            OpaqueDataDictionary processorParameters = new OpaqueDataDictionary();

            return p_context.Convert<MaterialContent, MaterialContent>(
                p_material, "CustomMaterialProcessor", processorParameters);
        }
    }
}